
class Computed {
  constructor() {
    /**
     * total () {
     *   console.log('Computed');
     *   return this.a + this.b;
     * },
     * 
     * {
     *   key: total,
     *   value: 3,
     *   get: total fn
     *   dep: [a, b] // dependencies
     * }
     */
    this.computedData = [];
  }

  addComputed (vm, computed, key) {
    // 函数单一化，每次只执行一个key
    const descriptor = Object.getOwnPropertyDescriptor(computed, key),
          // 把属性方法取出来(需要判断其get是否存在)
          descriptorFn = descriptor.value.get
                       ? descriptor.value.get
                       : descriptor.value,
          // 取出来之后指向window 需要改指向
          value = descriptorFn.call(vm),
          get = descriptorFn.bind(vm),
          // 依赖收集需要一个处理函数
          dep = this._collectDep(descriptorFn);
    // console.log(descriptor);
    // console.log('descriptorFn:', descriptorFn);
    // console.log('value:', value);
    // console.log('get:', get);
    // console.log('dep:', dep);
    this._addComputedProp({
      key,
      value,
      get,
      dep
    });
    // console.log(this.computedData);
    // 找到当前的key对应的数据
    const dataItem = this.computedData.find(item => item.key === key);
    // 把单个key 挂载到实例(vm -> this)上
    Object.defineProperty(vm, key, {
      get() {
        return dataItem.value;
      },
      set(newVal) { // 无论传什么值 都要保证是计算(get方法)出来的结果
        dataItem.value = dataItem.get();
      }
    })
  }

  update(key, cb) { // 更新(数据set的时候执行) -> 找到对应的key
    this.computedData.map(item => {
      const dep = item.dep; // 先取出依赖
      const _key = dep.find(el => el === key); // 查看这个依赖里是否存在这个变更的key

      if (_key) { // 如果存在就执行get方法更新
        const oldVal = item.value;
        item.value = item.get();
        // 可传入个回调函数，方便进行扩展
        cb && cb(item.key, item.value, oldVal);
      }
    })
  }

  _addComputedProp(computedProp) { // 尽量写成方法，方便日后扩展
    this.computedData.push(computedProp);
  }

  _collectDep(fn) {
    const matched = fn.toString().match(/this\.(.+?)/g);
    // console.log(matched); // ["this.a", "this.b"]
    // 返回 this.xxx 之后的这个xxx
    return matched.map(item => item.split('.')[1]);
  }

}

export default Computed;